using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Evolution.Engine.Managers;
using Evolution.Engine.Interfaces;
using System.IO;

namespace Evolution.Engine.Objects
{
    public class Terrain : BaseObject, ILoadable, IRenderable, Evolution.Engine.Interfaces.IUpdateable, IHasMaterial
    {
        public struct VertexMultitextured
        {
            public Vector3 Position;
            public Vector3 Normal;
            public Vector4 TextureCoordinate;
            public Vector4 TexWeights;

            public static int SizeInBytes = (3 + 3 + 4 + 4) * 4;
            public static VertexElement[] VertexElements = new VertexElement[]
             {
                 new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
                 new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
                 new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ),
                 new VertexElement( 0, sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ),
             };
        }

        private VertexBuffer _Vb;
        private IndexBuffer _Ib;
        VertexMultitextured[] _Vertices;
        private int _Height;
        private int _Width;
        private float[] _HeightData;
        private string _HeightAsset;
        private string[] _Assets;
        private Texture2D _HeightMap;
        private Texture2D[] _Textures;
        private bool _IsLoaded = false;

        public bool IsLoaded { get { return _IsLoaded; } set { _IsLoaded = value; } }
        public int Height { get { return _Height; } }
        public int Width { get { return _Width; } }

        public Terrain(string[] textureAssets, string heightAsset, string name)
            : base(name)
        {
            _HeightAsset = heightAsset;

            _Assets = new string[textureAssets.Length];
            for (int ass = 0; ass < textureAssets.Length; ass++)
                _Assets[ass] = textureAssets[ass];
        }

        public float GetHeightAt(Vector3 objPosition)
        {
            float fTopLeft, fTopRight, fBottomLeft, fBottomRight;
            float x, z;

            x = objPosition.X / Scale.X;
            z = objPosition.Z / Scale.Z;

            try
            {

                if ((x <= 0 && z <= 0) && (x >= _Width && z >= _Height)) return 0f;

                int ix = (int)x;
                int iz = (int)z;

                // Calculate the remainder (percent across quad)
                float fPercentX = x - ((float)ix);
                float fPercentZ = z - ((float)iz);

                fTopRight = _HeightData[(ix + 1) + iz * _Width] * Scale.Y;
                fBottomLeft = _HeightData[ix + (iz + 1) * _Width] * Scale.Y;

                // Calculate which triangle of the quad are we in ?
                if (fPercentX < (1.0f - fPercentZ))
                {
                    fTopLeft = _HeightData[ix + iz * _Width] * Scale.Y;
                    fBottomRight = fBottomLeft + (fTopRight - fTopLeft);

                } // End if Left Triangle
                else
                {
                    fBottomRight = _HeightData[(ix + 1) + (iz + 1) * _Width] * Scale.Y;
                    fTopLeft = fTopRight + (fBottomLeft - fBottomRight);

                } // End if Right Triangle

                // Calculate the height interpolated across the top and bottom edges
                float fTopHeight = fTopLeft + ((fTopRight - fTopLeft) * fPercentX);
                float fBottomHeight = fBottomLeft + ((fBottomRight - fBottomLeft) * fPercentX);

                // Calculate the resulting height interpolated between the two heights
                return fTopHeight + ((fBottomHeight - fTopHeight) * fPercentZ);
            }
            catch
            {
                return 0f;
            }
        }

        public override void LoadContent()
        {
            if (_IsLoaded)
                return;

            // Textures
            _Textures = new Texture2D[_Assets.Length];
            for (int ass = 0; ass < _Assets.Length; ass++)
                _Textures[ass] = ResourceManager.Instance.LoadContent<Texture2D>(_Assets[ass]);

            // Height Data
            _HeightMap = ResourceManager.Instance.LoadContent<Texture2D>(_HeightAsset);
            LoadHeightData();

            // Vertices
            _Vertices = new VertexMultitextured[_Width * _Height];

            for (int x = 0; x < _Width; x++)
            {
                for (int y = 0; y < _Height; y++)
                {
                    _Vertices[x + y * _Width].Position = new Vector3(x, _HeightData[x + y * _Width], y);
                    _Vertices[x + y * _Width].Normal = new Vector3(0, 1, 0);
                    _Vertices[x + y * _Width].TextureCoordinate.X = (float)x / EnvironmentManager.Instance.BlockSize;
                    _Vertices[x + y * _Width].TextureCoordinate.Y = (float)y / EnvironmentManager.Instance.BlockSize;

                    _Vertices[x + y * _Width].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(_HeightData[x + y * _Width] - 0) / 8.0f, 0, 1);
                    _Vertices[x + y * _Width].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(_HeightData[x + y * _Width] - 12) / 6.0f, 0, 1);
                    _Vertices[x + y * _Width].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(_HeightData[x + y * _Width] - 20) / 6.0f, 0, 1);
                    _Vertices[x + y * _Width].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(_HeightData[x + y * _Width] - 30) / 6.0f, 0, 1);

                    float totalWeight = _Vertices[x + y * _Width].TexWeights.X;
                    totalWeight += _Vertices[x + y * _Width].TexWeights.Z;
                    totalWeight += _Vertices[x + y * _Width].TexWeights.Y;
                    totalWeight += _Vertices[x + y * _Width].TexWeights.W;

                    _Vertices[x + y * _Width].TexWeights.X /= totalWeight;
                    _Vertices[x + y * _Width].TexWeights.Z /= totalWeight;
                    _Vertices[x + y * _Width].TexWeights.Y /= totalWeight;
                    _Vertices[x + y * _Width].TexWeights.W /= totalWeight;
                }
            }

            for (int x = 1; x < _Width - 1; x++)
            {
                for (int y = 1; y < _Height - 1; y++)
                {
                    Vector3 normX = new Vector3((_Vertices[x - 1 + y * _Width].Position.Y - _Vertices[x + 1 + y * _Width].Position.Y) / 2, 0, 1);
                    Vector3 normY = new Vector3(0, 1, (_Vertices[x + (y - 1) * _Width].Position.Y - _Vertices[x + (y + 1) * _Width].Position.Y) / 2);
                    _Vertices[x + y * _Width].Normal = normX + normY;
                    _Vertices[x + y * _Width].Normal.Normalize();
                }
            }

            _Vb = new VertexBuffer(GameEngine.Instance.Device, VertexMultitextured.SizeInBytes * _Width * _Height, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic);
            _Vb.SetData(_Vertices);

            //Index
            int[] terrainIndices = new int[(_Width - 1) * (_Height - 1) * 6];
            for (int x = 0; x < _Width - 1; x++)
            {
                for (int y = 0; y < _Height - 1; y++)
                {
                    terrainIndices[(x + y * (_Width - 1)) * 6] = (int)((x + 1) + (y + 1) * _Width);
                    terrainIndices[(x + y * (_Width - 1)) * 6 + 1] = (int)((x + 1) + y * _Width);
                    terrainIndices[(x + y * (_Width - 1)) * 6 + 2] = (int)(x + y * _Width);

                    terrainIndices[(x + y * (_Width - 1)) * 6 + 3] = (int)((x + 1) + (y + 1) * _Width);
                    terrainIndices[(x + y * (_Width - 1)) * 6 + 4] = (int)(x + y * _Width);
                    terrainIndices[(x + y * (_Width - 1)) * 6 + 5] = (int)(x + (y + 1) * _Width);
                }
            }

            _Ib = new IndexBuffer(GameEngine.Instance.Device, typeof(int), (_Width - 1) * (_Height - 1) * 6, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic);
            _Ib.SetData(terrainIndices);

            _IsLoaded = true;
        }

        private void LoadHeightData()
        {
            float minimumHeight = 255;
            float maximumHeight = 0;

            _Width = _HeightMap.Width;
            _Height = _HeightMap.Height;
            Color[] heightMapColors = new Color[_Width * _Height];
            _HeightMap.GetData(heightMapColors);

            _HeightData = new float[_Width * _Height];
            for (int x = 0; x < _Width; x++)
            {
                for (int y = 0; y < _Height; y++)
                {
                    _HeightData[x + y * _Width] = heightMapColors[x + y * _Width].R;
                    if (_HeightData[x + y * _Width] < minimumHeight) minimumHeight = _HeightData[x + y * _Width];
                    if (_HeightData[x + y * _Width] > maximumHeight) maximumHeight = _HeightData[x + y * _Width];
                }
            }

            for (int x = 0; x < _Width; x++)
                for (int y = 0; y < _Height; y++)
                    _HeightData[x + y * _Width] = (_HeightData[x + y * _Width] - minimumHeight) / (maximumHeight - minimumHeight) * EnvironmentManager.Instance.BlockHeight;

            _Position = new Vector3(0f);
        }
        public void Render(GameTime gameTime)
        {
            Effect effect = ShaderManager.Instance.GetShader(_Shader).Effect;

            if (effect.Parameters["LightPosition"] != null)
                effect.Parameters["LightPosition"].SetValue(EnvironmentManager.Instance.SkyObjects[0].Position);

            if (effect.Parameters["SandTexture"] != null)
                effect.Parameters["SandTexture"].SetValue(_Textures[0]);

            if (effect.Parameters["GrassTexture"] != null)
                effect.Parameters["GrassTexture"].SetValue(_Textures[1]);

            if (effect.Parameters["RockTexture"] != null)
                effect.Parameters["RockTexture"].SetValue(_Textures[2]);

            if (effect.Parameters["SnowTexture"] != null)
                effect.Parameters["SnowTexture"].SetValue(_Textures[3]);

            CullMode cull = GameEngine.Instance.Device.RenderState.CullMode;
            if (GameEngine.Instance.Device.RenderState.CullMode != CullMode.None)
                GameEngine.Instance.Device.RenderState.CullMode = CullMode.None;

            if (GameEngine.Instance.Device.RenderState.AlphaBlendEnable)
                GameEngine.Instance.Device.RenderState.AlphaBlendEnable = false;

            GameEngine.Instance.Device.Vertices[0].SetSource(_Vb, 0, VertexMultitextured.SizeInBytes);
            GameEngine.Instance.Device.Indices = _Ib;
            GameEngine.Instance.Device.VertexDeclaration = new VertexDeclaration(GameEngine.Instance.Device, VertexMultitextured.VertexElements);
            GameEngine.Instance.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _Width * _Height, 0, (_Width - 1) * (_Height - 1) * 2);

            if (GameEngine.Instance.Device.RenderState.CullMode != cull)
                GameEngine.Instance.Device.RenderState.CullMode = cull;
        }

        #region IUpdateable Members

        void Evolution.Engine.Interfaces.IUpdateable.Update(GameTime gameTime)
        {
            TextManager.Instance.Write("Default", "Terrain : " + _Position.ToString() + " " + _Width.ToString() + ":" + _Height.ToString(), new Vector2(10, 40), Color.White);
        }

        #endregion

        #region IHasMaterial Members

        public void SetMaterialProperties()
        {

        }

        #endregion
    }
}
